Week 8 — Input Devices

Oct 29, 2025 · PCB Time of Flight Distance Sensor Fusion 360

Overview

Considering what my final project needs, I decided to add another PCB module this week. This assignment focuses on creating a distance sensor that measures physical distance and displays the readings on a screen. The sensor I chose is the VL53L1X, which I selected for its clear pin labeling. After researching how this sensor works, I determined that I only needed four pins to make it functional: VDD, GND, SDA, SCL.

I downloaded the XIAO ESP32S3 pinlist and VL53L1X pinlist to guide my PCB design.

VL53L1X Pin Overview

The VL53L1X has six pins. Here's what each one does:

VIN — Power input, typically 3.3V or 5V.

GND — Ground reference. Must be connected to the microcontroller's GND pin.

SCL — I²C clock line. Connect to the microcontroller's SCL pin. The microcontroller controls the clock pulses on this line.

SDA — I²C data line. Connect to the microcontroller's SDA pin. All data (commands and readings) flows through this line. Together, SCL and SDA form the entire I²C communication interface.

GPIO — A general-purpose output from the sensor. This pin is optional and can be used as an interrupt output, for example.

XSHUT — Shutdown pin (active-low hardware reset). Pull LOW to shut down the sensor, or pull HIGH (3.3V) to wake it up.

PCB Design

Distance sensor schematic with 4-pin header
Schematic design including a 4-pin header for the display
PCB layout design
PCB layout — one of the simplest connections, no 0Ω resistor needed

Board Milling

First milling attempt with shallow traces
The first board milling attempt had shallow or missing traces. Anthony mentioned this could be due to the drill not being tight enough or the board not being properly secured to the tabletop. I addressed both issues and tried again.
Second milling attempt
The second attempt was slightly better, but still not quite there yet
Attempting to salvage a used board. My milling time was significantly longer than others, so I removed the text section (which had no practical value) to speed up the process.
Successfully milled board
Finally, a board that works. Verified with a multimeter
Accidentally destroyed board
Unfortunately, due to carelessness or shaky hands, I destroyed a perfectly working board and had to make another one.

Files

Download Gerber Files

Programming & Testing

Before programming, I needed to install the VL53L1X library.

During initial testing with simple wire connections, I encountered an unexpected problem: I wrote a program to scan for I²C devices, and the result shocked me—the system detected 120+ devices! After research, I determined that my wire was too long. Long wires cause HIGH and LOW signals to drift into undefined floating states, creating noise. Once I switched to shorter wires, the problem disappeared and I detected only one device at address 0x29.

I2C scan showing one device detected
Now detecting one device instead of 120+
Arduino / C++ — I²C Device Scanner
#include <Wire.h>

// Change these if you're on a board where you can re-map I2C pins (ESP32, etc.)
#ifndef SDA
#define SDA -1
#endif
#ifndef SCL
#define SCL -1
#endif

static const uint8_t VL53_ADDR = 0x29;

// ---- Low-level I2C helpers for VL53L1X register access ----
// VL53L1X uses 16-bit register addresses.
bool writeReg16(uint8_t addr, uint16_t reg, uint8_t value) {
  Wire.beginTransmission(addr);
  Wire.write((uint8_t)(reg >> 8));
  Wire.write((uint8_t)(reg & 0xFF));
  Wire.write(value);
  return (Wire.endTransmission() == 0);
}

bool readReg8(uint8_t addr, uint16_t reg, uint8_t &out) {
  Wire.beginTransmission(addr);
  Wire.write((uint8_t)(reg >> 8));
  Wire.write((uint8_t)(reg & 0xFF));
  if (Wire.endTransmission(false) != 0) {
    return false;
  }

  int n = Wire.requestFrom((int)addr, 1);
  if (n != 1) return false;

  out = Wire.read();
  return true;
}

void i2cScan() {
  Serial.println("=== I2C scan ===");
  int found = 0;

  for (uint8_t a = 1; a < 127; a++) {
    Wire.beginTransmission(a);
    uint8_t err = Wire.endTransmission();
    if (err == 0) {
      Serial.print("  Found device at 0x");
      if (a < 16) Serial.print("0");
      Serial.println(a, HEX);
      found++;
    }
  }

  Serial.print("Total I2C devices: ");
  Serial.println(found);
  Serial.println("=== I2C scan end ===\n");
}

void setup() {
  Serial.begin(115200);
  delay(800);
  Serial.println("\nVL53L1X I2C + ID Test\n");

  Wire.begin();
  Wire.setClock(400000);

  i2cScan();

  Serial.println("Checking for device at 0x29...");
  Wire.beginTransmission(VL53_ADDR);
  uint8_t err = Wire.endTransmission();
  Serial.print("  endTransmission returned: ");
  Serial.println(err);

  if (err != 0) {
    Serial.println("\n❌ No ACK from 0x29.\n");
    return;
  }

  Serial.println("✅ 0x29 ACKed.\n");

  Serial.println("Reading Model ID (reg 0x010F)...");
  uint8_t modelId = 0;
  bool ok = readReg8(VL53_ADDR, 0x010F, modelId);

  if (!ok) {
    Serial.println("❌ Failed to read Model ID.");
    return;
  }

  Serial.print("✅ Model ID read: 0x");
  Serial.println(modelId, HEX);
}

void loop() {
  delay(3000);
  i2cScan();
}

Testing the Sensor

Since the connection was simple and I had experienced a major setback while soldering the board, I decided to first connect the electronics with temporary wires and write a test program to verify the sensor works. The results were surprisingly smooth—it worked, though with a few caveats I'll discuss shortly.

First test with the sensor responding successfully to distance measurements
Improving the refresh rate by reducing the loop delay for more responsive tracking
Final milled distance sensor board
Now confident the sensor works and understanding the pin connections, I milled the final board using Bantam in the lab.
Completed and soldered board
Board soldering complete

Initial Observations

1) Readings showed small variations (mm-level jitter)

Even when the sensor was placed on a stable table measuring the distance to the ceiling, the returned values were not identical each time. The readings were largely consistent, but each measurement varied by a few millimeters. This suggests the sensor has a small but noticeable measurement error or noise under real conditions.

2) "Zero" readings occurred before physical contact

When I moved my hand close to the sensor, it reported zero distance before physically touching it. I could get a reading of 0 even when my palm was approximately 3–4 cm away. This suggests there may be a minimum measurable range or clamping behavior—the sensor might report 0 below a certain threshold distance rather than reporting negative values. In other words, the displayed value may behave like: max(0, real_distance - threshold).

Display Integration

VL53L1X sensor specification PDF
Download sensor specification PDF
Arduino / C++ — OLED SSD1306 Display Test
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDR 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void setup() {
  Serial.begin(115200);

  // Use board default I2C pins
  Wire.begin();
  Wire.setClock(100000);

  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("SSD1306 init failed");
    while (true) delay(10);
  }

  display.clearDisplay();
  display.display();

  Serial.println("OLED initialized");
}

void loop() {
  static uint32_t counter = 0;

  display.clearDisplay();

  // Visual proof it works
  display.drawRect(0, 0, 128, 64, SSD1306_WHITE);
  display.fillRect(5, 5, 30, 20, SSD1306_WHITE);

  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(2);
  display.setCursor(45, 10);
  display.println("TEST");

  display.setTextSize(1);
  display.setCursor(45, 40);
  display.print("Count: ");
  display.println(counter++);

  display.display();
  delay(200);
}